Skip to main content

Visibility

Visibility controls who is allowed to access what.

Rust’s philosophy:

Everything is private by default.

You must explicitly allow access using pub.

Visibility applies to:

  • modules
  • functions
  • structs
  • enums
  • fields
  • methods
  • constants
  • traits

The pub Keyword – What It Means​

pub means:

“This item can be accessed from outside the current module.”

But pub does NOT mean globally accessible.

Visibility is still constrained by:

  • module hierarchy
  • crate boundaries

The Golden Rule of Visibility​

You can only access an item if every module in the path is visible.

This is the most important rule to remember.

Private by Default (No pub)​

mod secrets {
fn hidden() {
println!("Top secret");
}
}

fn main() {
// secrets::hidden(); ❌ error
}

Why?

  • hidden() is private
  • Only accessible inside secrets

Making Functions Public​

mod secrets {
pub fn reveal() {
println!("Not so secret");
}
}

fn main() {
secrets::reveal(); // ✅
}

Module Visibility (pub mod)​

Modules themselves must also be public.

Common mistake​

mod api {
pub fn call() {}
}

fn main() {
// api::call(); ❌ api is private
}

Correct version​

pub mod api {
pub fn call() {}
}

fn main() {
api::call(); // ✅
}

Visibility Chains (Path Rule)​

Broken visibility chain​

mod outer {
mod inner {
pub fn run() {}
}
}

fn main() {
// outer::inner::run(); ❌
}

Fixed​

pub mod outer {
pub mod inner {
pub fn run() {}
}
}

fn main() {
outer::inner::run(); // ✅
}

Every level in the path must be pub

Struct Visibility​

Struct itself vs fields​

mod model {
pub struct User {
pub name: String,
age: u8,
}
}

What’s accessible?

use model::User;

fn main() {
let u = User {
name: "Alice".into(),
// age: 30 ❌ private field
};

println!("{}", u.name); // ✅
}

Key rule

  • pub struct → type is public
  • fields are private unless marked pub

Enum Visibility​

Enums are simpler.

pub enum Status {
Active,
Inactive,
}
  • All variants are automatically public
  • No need to mark each variant pub

Method Visibility (impl blocks)​

Methods follow the same rule.

pub struct Account {
balance: i32,
}

impl Account {
pub fn new() -> Self {
Self { balance: 0 }
}

fn secret_reset(&mut self) {
self.balance = 0;
}
}
  • new() → public API
  • secret_reset() → internal helper

Visibility with use​

use does not change visibility.

Wrong assumption

mod hidden {
fn secret() {}
}

use hidden::secret; // ❌ still private

Must be public first

mod hidden {
pub fn secret() {}
}

use hidden::secret; // ✅

pub(crate) – Crate-Level Visibility​

Rust offers fine-grained visibility.

pub(crate) fn internal_only() {}

Accessible:

  • anywhere inside the same crate
  • NOT from other crates

Other Visibility Modifiers​

ModifierAccessible From
pubEverywhere
pub(crate)Same crate
pub(super)Parent module
pub(self)Same module only
mod parent {
pub(super) fn call_parent() {}
}

File-Based Example (Real Structure)​

src/
├── lib.rs
└── auth/
├── mod.rs
└── login.rs

lib.rs

pub mod auth;

auth/mod.rs

pub mod login;

auth/login.rs

pub fn login_user() {
println!("Logged in");
}

fn helper() {} // private

External usage

use my_lib::auth::login::login_user;

Re-exporting with pub use​

This lets you control your public API.

mod internal {
pub struct User;
}

pub use internal::User;

Result

use my_lib::User;

Users don’t need to know about internal

Binary + Library Visibility Pattern​

Recommended design

  • Library crate
    • exposes pub APIs
    • hides internals
  • Binary crate
    • uses the public API only
// lib.rs
mod engine;
pub use engine::run;

Why Rust Is So Strict About Visibility​

Rust forces you to:

  • design clean APIs
  • avoid accidental coupling
  • make internal changes safely
  • think in terms of boundaries

This is a feature, not friction.